1
|
|
|
/* global kirkiControlLoader */ |
2
|
|
|
var RepeaterRow = function( rowIndex, container, label, control ) { |
|
|
|
|
3
|
|
|
|
4
|
|
|
'use strict'; |
5
|
|
|
|
6
|
|
|
var self = this; |
|
|
|
|
7
|
|
|
this.rowIndex = rowIndex; |
8
|
|
|
this.container = container; |
9
|
|
|
this.label = label; |
10
|
|
|
this.header = this.container.find( '.repeater-row-header' ), |
|
|
|
|
11
|
|
|
|
12
|
|
|
this.header.on( 'click', function() { |
13
|
|
|
self.toggleMinimize(); |
14
|
|
|
}); |
15
|
|
|
|
16
|
|
|
this.container.on( 'click', '.repeater-row-remove', function() { |
17
|
|
|
self.remove(); |
18
|
|
|
}); |
19
|
|
|
|
20
|
|
|
this.header.on( 'mousedown', function() { |
21
|
|
|
self.container.trigger( 'row:start-dragging' ); |
22
|
|
|
}); |
23
|
|
|
|
24
|
|
|
this.container.on( 'keyup change', 'input, select, textarea', function( e ) { |
25
|
|
|
self.container.trigger( 'row:update', [ self.rowIndex, jQuery( e.target ).data( 'field' ), e.target ] ); |
26
|
|
|
}); |
27
|
|
|
|
28
|
|
|
this.setRowIndex = function( rowIndex ) { |
29
|
|
|
this.rowIndex = rowIndex; |
30
|
|
|
this.container.attr( 'data-row', rowIndex ); |
31
|
|
|
this.container.data( 'row', rowIndex ); |
32
|
|
|
this.updateLabel(); |
33
|
|
|
}; |
34
|
|
|
|
35
|
|
|
this.toggleMinimize = function() { |
36
|
|
|
|
37
|
|
|
// Store the previous state. |
38
|
|
|
this.container.toggleClass( 'minimized' ); |
39
|
|
|
this.header.find( '.dashicons' ).toggleClass( 'dashicons-arrow-up' ).toggleClass( 'dashicons-arrow-down' ); |
40
|
|
|
}; |
41
|
|
|
|
42
|
|
|
this.remove = function() { |
43
|
|
|
this.container.slideUp( 300, function() { |
44
|
|
|
jQuery( this ).detach(); |
45
|
|
|
}); |
46
|
|
|
this.container.trigger( 'row:remove', [ this.rowIndex ] ); |
47
|
|
|
}; |
48
|
|
|
|
49
|
|
|
this.updateLabel = function() { |
50
|
|
|
var rowLabelField, |
|
|
|
|
51
|
|
|
rowLabel, |
52
|
|
|
rowLabelSelector; |
53
|
|
|
|
54
|
|
|
if ( 'field' === this.label.type ) { |
55
|
|
|
rowLabelField = this.container.find( '.repeater-field [data-field="' + this.label.field + '"]' ); |
56
|
|
|
if ( _.isFunction( rowLabelField.val ) ) { |
57
|
|
|
rowLabel = rowLabelField.val(); |
58
|
|
|
if ( '' !== rowLabel ) { |
59
|
|
|
if ( ! _.isUndefined( control.params.fields[ this.label.field ] ) ) { |
60
|
|
|
if ( ! _.isUndefined( control.params.fields[ this.label.field ].type ) ) { |
61
|
|
|
if ( 'select' === control.params.fields[ this.label.field ].type ) { |
62
|
|
|
if ( ! _.isUndefined( control.params.fields[ this.label.field ].choices ) && ! _.isUndefined( control.params.fields[ this.label.field ].choices[ rowLabelField.val() ] ) ) { |
63
|
|
|
rowLabel = control.params.fields[ this.label.field ].choices[ rowLabelField.val() ]; |
64
|
|
|
} |
65
|
|
|
} else if ( 'radio' === control.params.fields[ this.label.field ].type || 'radio-image' === control.params.fields[ this.label.field ].type ) { |
66
|
|
|
rowLabelSelector = control.selector + ' [data-row="' + this.rowIndex + '"] .repeater-field [data-field="' + this.label.field + '"]:checked'; |
67
|
|
|
rowLabel = jQuery( rowLabelSelector ).val(); |
68
|
|
|
} |
69
|
|
|
} |
70
|
|
|
} |
71
|
|
|
this.header.find( '.repeater-row-label' ).text( rowLabel ); |
72
|
|
|
return; |
73
|
|
|
} |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
this.header.find( '.repeater-row-label' ).text( this.label.value + ' ' + ( this.rowIndex + 1 ) ); |
77
|
|
|
}; |
78
|
|
|
this.updateLabel(); |
79
|
|
|
}; |
80
|
|
|
|
81
|
|
|
wp.customize.controlConstructor.repeater = wp.customize.Control.extend({ |
82
|
|
|
|
83
|
|
|
// When we're finished loading continue processing |
84
|
|
|
ready: function() { |
85
|
|
|
|
86
|
|
|
'use strict'; |
87
|
|
|
|
88
|
|
|
var control = this; |
|
|
|
|
89
|
|
|
|
90
|
|
|
// Init the control. |
91
|
|
|
if ( ! _.isUndefined( window.kirkiControlLoader ) && _.isFunction( kirkiControlLoader ) ) { |
92
|
|
|
kirkiControlLoader( control ); |
93
|
|
|
} else { |
94
|
|
|
control.initKirkiControl(); |
95
|
|
|
} |
96
|
|
|
}, |
97
|
|
|
|
98
|
|
|
initKirkiControl: function() { |
99
|
|
|
|
100
|
|
|
'use strict'; |
101
|
|
|
|
102
|
|
|
var control = this, |
|
|
|
|
103
|
|
|
limit, |
104
|
|
|
theNewRow; |
105
|
|
|
|
106
|
|
|
// The current value set in Control Class (set in Kirki_Customize_Repeater_Control::to_json() function) |
107
|
|
|
var settingValue = this.params.value; |
|
|
|
|
108
|
|
|
|
109
|
|
|
control.container.find( '.kirki-controls-loading-spinner' ).hide(); |
110
|
|
|
|
111
|
|
|
// The hidden field that keeps the data saved (though we never update it) |
112
|
|
|
this.settingField = this.container.find( '[data-customize-setting-link]' ).first(); |
113
|
|
|
|
114
|
|
|
// Set the field value for the first time, we'll fill it up later |
115
|
|
|
this.setValue( [], false ); |
116
|
|
|
|
117
|
|
|
// The DIV that holds all the rows |
118
|
|
|
this.repeaterFieldsContainer = this.container.find( '.repeater-fields' ).first(); |
119
|
|
|
|
120
|
|
|
// Set number of rows to 0 |
121
|
|
|
this.currentIndex = 0; |
122
|
|
|
|
123
|
|
|
// Save the rows objects |
124
|
|
|
this.rows = []; |
125
|
|
|
|
126
|
|
|
// Default limit choice |
127
|
|
|
limit = false; |
128
|
|
|
if ( ! _.isUndefined( this.params.choices.limit ) ) { |
129
|
|
|
limit = ( 0 >= this.params.choices.limit ) ? false : parseInt( this.params.choices.limit, 10 ); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
this.container.on( 'click', 'button.repeater-add', function( e ) { |
133
|
|
|
e.preventDefault(); |
134
|
|
|
if ( ! limit || control.currentIndex < limit ) { |
135
|
|
|
theNewRow = control.addRow(); |
136
|
|
|
theNewRow.toggleMinimize(); |
137
|
|
|
control.initColorPicker(); |
138
|
|
|
control.initSelect( theNewRow ); |
139
|
|
|
} else { |
140
|
|
|
jQuery( control.selector + ' .limit' ).addClass( 'highlight' ); |
141
|
|
|
} |
142
|
|
|
}); |
143
|
|
|
|
144
|
|
|
this.container.on( 'click', '.repeater-row-remove', function() { |
145
|
|
|
control.currentIndex--; |
146
|
|
|
if ( ! limit || control.currentIndex < limit ) { |
147
|
|
|
jQuery( control.selector + ' .limit' ).removeClass( 'highlight' ); |
148
|
|
|
} |
149
|
|
|
}); |
150
|
|
|
|
151
|
|
|
this.container.on( 'click keypress', '.repeater-field-image .upload-button,.repeater-field-cropped_image .upload-button,.repeater-field-upload .upload-button', function( e ) { |
152
|
|
|
e.preventDefault(); |
153
|
|
|
control.$thisButton = jQuery( this ); |
154
|
|
|
control.openFrame( e ); |
155
|
|
|
}); |
156
|
|
|
|
157
|
|
|
this.container.on( 'click keypress', '.repeater-field-image .remove-button,.repeater-field-cropped_image .remove-button', function( e ) { |
158
|
|
|
e.preventDefault(); |
159
|
|
|
control.$thisButton = jQuery( this ); |
160
|
|
|
control.removeImage( e ); |
161
|
|
|
}); |
162
|
|
|
|
163
|
|
|
this.container.on( 'click keypress', '.repeater-field-upload .remove-button', function( e ) { |
164
|
|
|
e.preventDefault(); |
165
|
|
|
control.$thisButton = jQuery( this ); |
166
|
|
|
control.removeFile( e ); |
167
|
|
|
}); |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* Function that loads the Mustache template |
171
|
|
|
*/ |
172
|
|
|
this.repeaterTemplate = _.memoize( function() { |
173
|
|
|
var compiled, |
|
|
|
|
174
|
|
|
/* |
175
|
|
|
* Underscore's default ERB-style templates are incompatible with PHP |
176
|
|
|
* when asp_tags is enabled, so WordPress uses Mustache-inspired templating syntax. |
177
|
|
|
* |
178
|
|
|
* @see trac ticket #22344. |
179
|
|
|
*/ |
180
|
|
|
options = { |
181
|
|
|
evaluate: /<#([\s\S]+?)#>/g, |
182
|
|
|
interpolate: /\{\{\{([\s\S]+?)\}\}\}/g, |
183
|
|
|
escape: /\{\{([^\}]+?)\}\}(?!\})/g, |
184
|
|
|
variable: 'data' |
185
|
|
|
}; |
186
|
|
|
|
187
|
|
|
return function( data ) { |
188
|
|
|
compiled = _.template( control.container.find( '.customize-control-repeater-content' ).first().html(), null, options ); |
189
|
|
|
return compiled( data ); |
190
|
|
|
}; |
191
|
|
|
}); |
192
|
|
|
|
193
|
|
|
// When we load the control, the fields have not been filled up |
194
|
|
|
// This is the first time that we create all the rows |
195
|
|
|
if ( settingValue.length ) { |
196
|
|
|
_.each( settingValue, function( subValue ) { |
197
|
|
|
theNewRow = control.addRow( subValue ); |
198
|
|
|
control.initColorPicker(); |
199
|
|
|
control.initSelect( theNewRow, subValue ); |
200
|
|
|
}); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
// Once we have displayed the rows, we cleanup the values |
204
|
|
|
this.setValue( settingValue, true, true ); |
205
|
|
|
|
206
|
|
|
this.repeaterFieldsContainer.sortable({ |
207
|
|
|
handle: '.repeater-row-header', |
208
|
|
|
update: function() { |
209
|
|
|
control.sort(); |
210
|
|
|
} |
211
|
|
|
}); |
212
|
|
|
|
213
|
|
|
}, |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Open the media modal. |
217
|
|
|
*/ |
218
|
|
|
openFrame: function( event ) { |
219
|
|
|
|
220
|
|
|
'use strict'; |
221
|
|
|
|
222
|
|
|
if ( wp.customize.utils.isKeydownButNotEnterEvent( event ) ) { |
223
|
|
|
return; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
if ( this.$thisButton.closest( '.repeater-field' ).hasClass( 'repeater-field-cropped_image' ) ) { |
227
|
|
|
this.initCropperFrame(); |
228
|
|
|
} else { |
229
|
|
|
this.initFrame(); |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
this.frame.open(); |
233
|
|
|
}, |
234
|
|
|
|
235
|
|
|
initFrame: function() { |
236
|
|
|
|
237
|
|
|
'use strict'; |
238
|
|
|
|
239
|
|
|
var libMediaType = this.getMimeType(); |
|
|
|
|
240
|
|
|
|
241
|
|
|
this.frame = wp.media({ |
242
|
|
|
states: [ |
243
|
|
|
new wp.media.controller.Library({ |
244
|
|
|
library: wp.media.query({ type: libMediaType }), |
245
|
|
|
multiple: false, |
246
|
|
|
date: false |
247
|
|
|
}) |
248
|
|
|
] |
249
|
|
|
}); |
250
|
|
|
|
251
|
|
|
// When a file is selected, run a callback. |
252
|
|
|
this.frame.on( 'select', this.onSelect, this ); |
253
|
|
|
}, |
254
|
|
|
/** |
255
|
|
|
* Create a media modal select frame, and store it so the instance can be reused when needed. |
256
|
|
|
* This is mostly a copy/paste of Core api.CroppedImageControl in /wp-admin/js/customize-control.js |
257
|
|
|
*/ |
258
|
|
|
initCropperFrame: function() { |
259
|
|
|
|
260
|
|
|
'use strict'; |
261
|
|
|
|
262
|
|
|
// We get the field id from which this was called |
263
|
|
|
var currentFieldId = this.$thisButton.siblings( 'input.hidden-field' ).attr( 'data-field' ), |
|
|
|
|
264
|
|
|
attrs = [ 'width', 'height', 'flex_width', 'flex_height' ], // A list of attributes to look for |
265
|
|
|
libMediaType = this.getMimeType(); |
266
|
|
|
|
267
|
|
|
// Make sure we got it |
268
|
|
|
if ( _.isString( currentFieldId ) && '' !== currentFieldId ) { |
269
|
|
|
|
270
|
|
|
// Make fields is defined and only do the hack for cropped_image |
271
|
|
|
if ( _.isObject( this.params.fields[ currentFieldId ] ) && 'cropped_image' === this.params.fields[ currentFieldId ].type ) { |
272
|
|
|
|
273
|
|
|
//Iterate over the list of attributes |
274
|
|
|
attrs.forEach( function( el ) { |
275
|
|
|
|
276
|
|
|
// If the attribute exists in the field |
277
|
|
|
if ( ! _.isUndefined( this.params.fields[ currentFieldId ][ el ] ) ) { |
278
|
|
|
|
279
|
|
|
// Set the attribute in the main object |
280
|
|
|
this.params[ el ] = this.params.fields[ currentFieldId ][ el ]; |
281
|
|
|
} |
282
|
|
|
}.bind( this ) ); |
283
|
|
|
} |
284
|
|
|
} |
285
|
|
|
|
286
|
|
|
this.frame = wp.media({ |
287
|
|
|
button: { |
288
|
|
|
text: 'Select and Crop', |
289
|
|
|
close: false |
290
|
|
|
}, |
291
|
|
|
states: [ |
292
|
|
|
new wp.media.controller.Library({ |
293
|
|
|
library: wp.media.query({ type: libMediaType }), |
294
|
|
|
multiple: false, |
295
|
|
|
date: false, |
296
|
|
|
suggestedWidth: this.params.width, |
297
|
|
|
suggestedHeight: this.params.height |
298
|
|
|
}), |
299
|
|
|
new wp.media.controller.CustomizeImageCropper({ |
300
|
|
|
imgSelectOptions: this.calculateImageSelectOptions, |
301
|
|
|
control: this |
302
|
|
|
}) |
303
|
|
|
] |
304
|
|
|
}); |
305
|
|
|
|
306
|
|
|
this.frame.on( 'select', this.onSelectForCrop, this ); |
307
|
|
|
this.frame.on( 'cropped', this.onCropped, this ); |
308
|
|
|
this.frame.on( 'skippedcrop', this.onSkippedCrop, this ); |
309
|
|
|
|
310
|
|
|
}, |
311
|
|
|
|
312
|
|
|
onSelect: function() { |
313
|
|
|
|
314
|
|
|
'use strict'; |
315
|
|
|
|
316
|
|
|
var attachment = this.frame.state().get( 'selection' ).first().toJSON(); |
|
|
|
|
317
|
|
|
|
318
|
|
|
if ( this.$thisButton.closest( '.repeater-field' ).hasClass( 'repeater-field-upload' ) ) { |
319
|
|
|
this.setFileInRepeaterField( attachment ); |
320
|
|
|
} else { |
321
|
|
|
this.setImageInRepeaterField( attachment ); |
322
|
|
|
} |
323
|
|
|
}, |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* After an image is selected in the media modal, switch to the cropper |
327
|
|
|
* state if the image isn't the right size. |
328
|
|
|
*/ |
329
|
|
|
|
330
|
|
|
onSelectForCrop: function() { |
331
|
|
|
|
332
|
|
|
'use strict'; |
333
|
|
|
|
334
|
|
|
var attachment = this.frame.state().get( 'selection' ).first().toJSON(); |
|
|
|
|
335
|
|
|
|
336
|
|
|
if ( this.params.width === attachment.width && this.params.height === attachment.height && ! this.params.flex_width && ! this.params.flex_height ) { |
337
|
|
|
this.setImageInRepeaterField( attachment ); |
338
|
|
|
} else { |
339
|
|
|
this.frame.setState( 'cropper' ); |
340
|
|
|
} |
341
|
|
|
}, |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* After the image has been cropped, apply the cropped image data to the setting. |
345
|
|
|
* |
346
|
|
|
* @param {object} croppedImage Cropped attachment data. |
347
|
|
|
*/ |
348
|
|
|
onCropped: function( croppedImage ) { |
349
|
|
|
|
350
|
|
|
'use strict'; |
351
|
|
|
|
352
|
|
|
this.setImageInRepeaterField( croppedImage ); |
353
|
|
|
|
354
|
|
|
}, |
355
|
|
|
|
356
|
|
|
/** |
357
|
|
|
* Returns a set of options, computed from the attached image data and |
358
|
|
|
* control-specific data, to be fed to the imgAreaSelect plugin in |
359
|
|
|
* wp.media.view.Cropper. |
360
|
|
|
* |
361
|
|
|
* @param {wp.media.model.Attachment} attachment |
362
|
|
|
* @param {wp.media.controller.Cropper} controller |
363
|
|
|
* @returns {Object} Options |
364
|
|
|
*/ |
365
|
|
|
calculateImageSelectOptions: function( attachment, controller ) { |
366
|
|
|
|
367
|
|
|
'use strict'; |
368
|
|
|
|
369
|
|
|
var control = controller.get( 'control' ), |
|
|
|
|
370
|
|
|
flexWidth = !! parseInt( control.params.flex_width, 10 ), |
371
|
|
|
flexHeight = !! parseInt( control.params.flex_height, 10 ), |
372
|
|
|
realWidth = attachment.get( 'width' ), |
373
|
|
|
realHeight = attachment.get( 'height' ), |
374
|
|
|
xInit = parseInt( control.params.width, 10 ), |
375
|
|
|
yInit = parseInt( control.params.height, 10 ), |
376
|
|
|
ratio = xInit / yInit, |
377
|
|
|
xImg = realWidth, |
378
|
|
|
yImg = realHeight, |
379
|
|
|
x1, |
380
|
|
|
y1, |
381
|
|
|
imgSelectOptions; |
382
|
|
|
|
383
|
|
|
controller.set( 'canSkipCrop', ! control.mustBeCropped( flexWidth, flexHeight, xInit, yInit, realWidth, realHeight ) ); |
384
|
|
|
|
385
|
|
|
if ( xImg / yImg > ratio ) { |
386
|
|
|
yInit = yImg; |
387
|
|
|
xInit = yInit * ratio; |
388
|
|
|
} else { |
389
|
|
|
xInit = xImg; |
390
|
|
|
yInit = xInit / ratio; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
x1 = ( xImg - xInit ) / 2; |
394
|
|
|
y1 = ( yImg - yInit ) / 2; |
395
|
|
|
|
396
|
|
|
imgSelectOptions = { |
397
|
|
|
handles: true, |
398
|
|
|
keys: true, |
399
|
|
|
instance: true, |
400
|
|
|
persistent: true, |
401
|
|
|
imageWidth: realWidth, |
402
|
|
|
imageHeight: realHeight, |
403
|
|
|
x1: x1, |
404
|
|
|
y1: y1, |
405
|
|
|
x2: xInit + x1, |
406
|
|
|
y2: yInit + y1 |
407
|
|
|
}; |
408
|
|
|
|
409
|
|
|
if ( false === flexHeight && false === flexWidth ) { |
410
|
|
|
imgSelectOptions.aspectRatio = xInit + ':' + yInit; |
411
|
|
|
} |
412
|
|
|
if ( false === flexHeight ) { |
413
|
|
|
imgSelectOptions.maxHeight = yInit; |
414
|
|
|
} |
415
|
|
|
if ( false === flexWidth ) { |
416
|
|
|
imgSelectOptions.maxWidth = xInit; |
417
|
|
|
} |
418
|
|
|
|
419
|
|
|
return imgSelectOptions; |
420
|
|
|
}, |
421
|
|
|
|
422
|
|
|
/** |
423
|
|
|
* Return whether the image must be cropped, based on required dimensions. |
424
|
|
|
* |
425
|
|
|
* @param {bool} flexW |
426
|
|
|
* @param {bool} flexH |
427
|
|
|
* @param {int} dstW |
428
|
|
|
* @param {int} dstH |
429
|
|
|
* @param {int} imgW |
430
|
|
|
* @param {int} imgH |
431
|
|
|
* @return {bool} |
432
|
|
|
*/ |
433
|
|
|
mustBeCropped: function( flexW, flexH, dstW, dstH, imgW, imgH ) { |
434
|
|
|
|
435
|
|
|
'use strict'; |
436
|
|
|
|
437
|
|
|
if ( ( true === flexW && true === flexH ) || ( true === flexW && dstH === imgH ) || ( true === flexH && dstW === imgW ) || ( dstW === imgW && dstH === imgH ) || ( imgW <= dstW ) ) { |
438
|
|
|
return false; |
439
|
|
|
} |
440
|
|
|
|
441
|
|
|
return true; |
442
|
|
|
}, |
443
|
|
|
|
444
|
|
|
/** |
445
|
|
|
* If cropping was skipped, apply the image data directly to the setting. |
446
|
|
|
*/ |
447
|
|
|
onSkippedCrop: function() { |
448
|
|
|
|
449
|
|
|
'use strict'; |
450
|
|
|
|
451
|
|
|
var attachment = this.frame.state().get( 'selection' ).first().toJSON(); |
|
|
|
|
452
|
|
|
this.setImageInRepeaterField( attachment ); |
453
|
|
|
|
454
|
|
|
}, |
455
|
|
|
|
456
|
|
|
/** |
457
|
|
|
* Updates the setting and re-renders the control UI. |
458
|
|
|
* |
459
|
|
|
* @param {object} attachment |
460
|
|
|
*/ |
461
|
|
|
setImageInRepeaterField: function( attachment ) { |
462
|
|
|
|
463
|
|
|
'use strict'; |
464
|
|
|
|
465
|
|
|
var $targetDiv = this.$thisButton.closest( '.repeater-field-image,.repeater-field-cropped_image' ); |
|
|
|
|
466
|
|
|
|
467
|
|
|
$targetDiv.find( '.kirki-image-attachment' ).html( '<img src="' + attachment.url + '">' ).hide().slideDown( 'slow' ); |
468
|
|
|
|
469
|
|
|
$targetDiv.find( '.hidden-field' ).val( attachment.id ); |
470
|
|
|
this.$thisButton.text( this.$thisButton.data( 'alt-label' ) ); |
471
|
|
|
$targetDiv.find( '.remove-button' ).show(); |
472
|
|
|
|
473
|
|
|
//This will activate the save button |
474
|
|
|
$targetDiv.find( 'input, textarea, select' ).trigger( 'change' ); |
475
|
|
|
this.frame.close(); |
476
|
|
|
|
477
|
|
|
}, |
478
|
|
|
|
479
|
|
|
/** |
480
|
|
|
* Updates the setting and re-renders the control UI. |
481
|
|
|
* |
482
|
|
|
* @param {object} attachment |
483
|
|
|
*/ |
484
|
|
|
setFileInRepeaterField: function( attachment ) { |
485
|
|
|
|
486
|
|
|
'use strict'; |
487
|
|
|
|
488
|
|
|
var $targetDiv = this.$thisButton.closest( '.repeater-field-upload' ); |
|
|
|
|
489
|
|
|
|
490
|
|
|
$targetDiv.find( '.kirki-file-attachment' ).html( '<span class="file"><span class="dashicons dashicons-media-default"></span> ' + attachment.filename + '</span>' ).hide().slideDown( 'slow' ); |
491
|
|
|
|
492
|
|
|
$targetDiv.find( '.hidden-field' ).val( attachment.id ); |
493
|
|
|
this.$thisButton.text( this.$thisButton.data( 'alt-label' ) ); |
494
|
|
|
$targetDiv.find( '.upload-button' ).show(); |
495
|
|
|
$targetDiv.find( '.remove-button' ).show(); |
496
|
|
|
|
497
|
|
|
//This will activate the save button |
498
|
|
|
$targetDiv.find( 'input, textarea, select' ).trigger( 'change' ); |
499
|
|
|
this.frame.close(); |
500
|
|
|
|
501
|
|
|
}, |
502
|
|
|
|
503
|
|
|
getMimeType: function() { |
504
|
|
|
|
505
|
|
|
'use strict'; |
506
|
|
|
|
507
|
|
|
// We get the field id from which this was called |
508
|
|
|
var currentFieldId = this.$thisButton.siblings( 'input.hidden-field' ).attr( 'data-field' ); |
|
|
|
|
509
|
|
|
|
510
|
|
|
// Make sure we got it |
511
|
|
|
if ( _.isString( currentFieldId ) && '' !== currentFieldId ) { |
512
|
|
|
|
513
|
|
|
// Make fields is defined and only do the hack for cropped_image |
514
|
|
|
if ( _.isObject( this.params.fields[ currentFieldId ] ) && 'upload' === this.params.fields[ currentFieldId ].type ) { |
515
|
|
|
|
516
|
|
|
// If the attribute exists in the field |
517
|
|
|
if ( ! _.isUndefined( this.params.fields[ currentFieldId ].mime_type ) ) { |
518
|
|
|
|
519
|
|
|
// Set the attribute in the main object |
520
|
|
|
return this.params.fields[ currentFieldId ].mime_type; |
521
|
|
|
} |
522
|
|
|
} |
523
|
|
|
} |
524
|
|
|
return 'image'; |
525
|
|
|
|
526
|
|
|
}, |
527
|
|
|
|
528
|
|
|
removeImage: function( event ) { |
529
|
|
|
|
530
|
|
|
'use strict'; |
531
|
|
|
|
532
|
|
|
var $targetDiv, |
|
|
|
|
533
|
|
|
$uploadButton; |
534
|
|
|
|
535
|
|
|
if ( wp.customize.utils.isKeydownButNotEnterEvent( event ) ) { |
536
|
|
|
return; |
537
|
|
|
} |
538
|
|
|
|
539
|
|
|
$targetDiv = this.$thisButton.closest( '.repeater-field-image,.repeater-field-cropped_image,.repeater-field-upload' ); |
540
|
|
|
$uploadButton = $targetDiv.find( '.upload-button' ); |
541
|
|
|
|
542
|
|
|
$targetDiv.find( '.kirki-image-attachment' ).slideUp( 'fast', function() { |
543
|
|
|
jQuery( this ).show().html( jQuery( this ).data( 'placeholder' ) ); |
544
|
|
|
}); |
545
|
|
|
$targetDiv.find( '.hidden-field' ).val( '' ); |
546
|
|
|
$uploadButton.text( $uploadButton.data( 'label' ) ); |
547
|
|
|
this.$thisButton.hide(); |
548
|
|
|
|
549
|
|
|
$targetDiv.find( 'input, textarea, select' ).trigger( 'change' ); |
550
|
|
|
|
551
|
|
|
}, |
552
|
|
|
|
553
|
|
|
removeFile: function( event ) { |
554
|
|
|
|
555
|
|
|
'use strict'; |
556
|
|
|
|
557
|
|
|
var $targetDiv, |
|
|
|
|
558
|
|
|
$uploadButton; |
559
|
|
|
|
560
|
|
|
if ( wp.customize.utils.isKeydownButNotEnterEvent( event ) ) { |
561
|
|
|
return; |
562
|
|
|
} |
563
|
|
|
|
564
|
|
|
$targetDiv = this.$thisButton.closest( '.repeater-field-upload' ); |
565
|
|
|
$uploadButton = $targetDiv.find( '.upload-button' ); |
566
|
|
|
|
567
|
|
|
$targetDiv.find( '.kirki-file-attachment' ).slideUp( 'fast', function() { |
568
|
|
|
jQuery( this ).show().html( jQuery( this ).data( 'placeholder' ) ); |
569
|
|
|
}); |
570
|
|
|
$targetDiv.find( '.hidden-field' ).val( '' ); |
571
|
|
|
$uploadButton.text( $uploadButton.data( 'label' ) ); |
572
|
|
|
this.$thisButton.hide(); |
573
|
|
|
|
574
|
|
|
$targetDiv.find( 'input, textarea, select' ).trigger( 'change' ); |
575
|
|
|
|
576
|
|
|
}, |
577
|
|
|
|
578
|
|
|
/** |
579
|
|
|
* Get the current value of the setting |
580
|
|
|
* |
581
|
|
|
* @return Object |
582
|
|
|
*/ |
583
|
|
|
getValue: function() { |
584
|
|
|
|
585
|
|
|
'use strict'; |
586
|
|
|
|
587
|
|
|
// The setting is saved in JSON |
588
|
|
|
return JSON.parse( decodeURI( this.setting.get() ) ); |
589
|
|
|
|
590
|
|
|
}, |
591
|
|
|
|
592
|
|
|
/** |
593
|
|
|
* Set a new value for the setting |
594
|
|
|
* |
595
|
|
|
* @param newValue Object |
596
|
|
|
* @param refresh If we want to refresh the previewer or not |
597
|
|
|
*/ |
598
|
|
|
setValue: function( newValue, refresh, filtering ) { |
599
|
|
|
|
600
|
|
|
'use strict'; |
601
|
|
|
|
602
|
|
|
// We need to filter the values after the first load to remove data requrired for diplay but that we don't want to save in DB |
603
|
|
|
var filteredValue = newValue, |
|
|
|
|
604
|
|
|
filter = []; |
605
|
|
|
|
606
|
|
|
if ( filtering ) { |
607
|
|
|
jQuery.each( this.params.fields, function( index, value ) { |
608
|
|
|
if ( 'image' === value.type || 'cropped_image' === value.type || 'upload' === value.type ) { |
609
|
|
|
filter.push( index ); |
610
|
|
|
} |
611
|
|
|
}); |
612
|
|
|
jQuery.each( newValue, function( index, value ) { |
613
|
|
|
jQuery.each( filter, function( ind, field ) { |
614
|
|
|
if ( ! _.isUndefined( value[ field ] ) && ! _.isUndefined( value[ field ].id ) ) { |
615
|
|
|
filteredValue[index][ field ] = value[ field ].id; |
616
|
|
|
} |
617
|
|
|
}); |
618
|
|
|
}); |
619
|
|
|
} |
620
|
|
|
|
621
|
|
|
this.setting.set( encodeURI( JSON.stringify( filteredValue ) ) ); |
622
|
|
|
|
623
|
|
|
if ( refresh ) { |
624
|
|
|
|
625
|
|
|
// Trigger the change event on the hidden field so |
626
|
|
|
// previewer refresh the website on Customizer |
627
|
|
|
this.settingField.trigger( 'change' ); |
628
|
|
|
} |
629
|
|
|
}, |
630
|
|
|
|
631
|
|
|
/** |
632
|
|
|
* Add a new row to repeater settings based on the structure. |
633
|
|
|
* |
634
|
|
|
* @param data (Optional) Object of field => value pairs (undefined if you want to get the default values) |
635
|
|
|
*/ |
636
|
|
|
addRow: function( data ) { |
637
|
|
|
|
638
|
|
|
'use strict'; |
639
|
|
|
|
640
|
|
|
var control = this, |
|
|
|
|
641
|
|
|
template = control.repeaterTemplate(), // The template for the new row (defined on Kirki_Customize_Repeater_Control::render_content() ). |
642
|
|
|
settingValue = this.getValue(), // Get the current setting value. |
643
|
|
|
newRowSetting = {}, // Saves the new setting data. |
644
|
|
|
templateData, // Data to pass to the template |
645
|
|
|
newRow, |
646
|
|
|
i; |
647
|
|
|
|
648
|
|
|
if ( template ) { |
|
|
|
|
649
|
|
|
|
650
|
|
|
// The control structure is going to define the new fields |
651
|
|
|
// We need to clone control.params.fields. Assigning it |
652
|
|
|
// ould result in a reference assignment. |
653
|
|
|
templateData = jQuery.extend( true, {}, control.params.fields ); |
654
|
|
|
|
655
|
|
|
// But if we have passed data, we'll use the data values instead |
656
|
|
|
if ( data ) { |
657
|
|
|
for ( i in data ) { |
658
|
|
|
if ( data.hasOwnProperty( i ) && templateData.hasOwnProperty( i ) ) { |
659
|
|
|
templateData[ i ]['default'] = data[ i ]; |
660
|
|
|
} |
661
|
|
|
} |
662
|
|
|
} |
663
|
|
|
|
664
|
|
|
templateData.index = this.currentIndex; |
665
|
|
|
|
666
|
|
|
// Append the template content |
667
|
|
|
template = template( templateData ); |
668
|
|
|
|
669
|
|
|
// Create a new row object and append the element |
670
|
|
|
newRow = new RepeaterRow( |
671
|
|
|
control.currentIndex, |
672
|
|
|
jQuery( template ).appendTo( control.repeaterFieldsContainer ), |
673
|
|
|
control.params.row_label, |
674
|
|
|
control |
675
|
|
|
); |
676
|
|
|
|
677
|
|
|
newRow.container.on( 'row:remove', function( e, rowIndex ) { |
678
|
|
|
control.deleteRow( rowIndex ); |
679
|
|
|
}); |
680
|
|
|
|
681
|
|
|
newRow.container.on( 'row:update', function( e, rowIndex, fieldName, element ) { |
682
|
|
|
control.updateField.call( control, e, rowIndex, fieldName, element ); |
683
|
|
|
newRow.updateLabel(); |
684
|
|
|
}); |
685
|
|
|
|
686
|
|
|
// Add the row to rows collection |
687
|
|
|
this.rows[ this.currentIndex ] = newRow; |
688
|
|
|
|
689
|
|
|
for ( i in templateData ) { |
690
|
|
|
if ( templateData.hasOwnProperty( i ) ) { |
691
|
|
|
newRowSetting[ i ] = templateData[ i ]['default']; |
692
|
|
|
} |
693
|
|
|
} |
694
|
|
|
|
695
|
|
|
settingValue[ this.currentIndex ] = newRowSetting; |
696
|
|
|
this.setValue( settingValue, true ); |
697
|
|
|
|
698
|
|
|
this.currentIndex++; |
699
|
|
|
|
700
|
|
|
return newRow; |
701
|
|
|
} |
702
|
|
|
}, |
703
|
|
|
|
704
|
|
|
sort: function() { |
705
|
|
|
|
706
|
|
|
'use strict'; |
707
|
|
|
|
708
|
|
|
var control = this, |
|
|
|
|
709
|
|
|
$rows = this.repeaterFieldsContainer.find( '.repeater-row' ), |
710
|
|
|
newOrder = [], |
711
|
|
|
settings = control.getValue(), |
712
|
|
|
newRows = [], |
713
|
|
|
newSettings = []; |
714
|
|
|
|
715
|
|
|
$rows.each( function( i, element ) { |
716
|
|
|
newOrder.push( jQuery( element ).data( 'row' ) ); |
717
|
|
|
}); |
718
|
|
|
|
719
|
|
|
jQuery.each( newOrder, function( newPosition, oldPosition ) { |
720
|
|
|
newRows[ newPosition ] = control.rows[ oldPosition ]; |
721
|
|
|
newRows[ newPosition ].setRowIndex( newPosition ); |
722
|
|
|
|
723
|
|
|
newSettings[ newPosition ] = settings[ oldPosition ]; |
724
|
|
|
}); |
725
|
|
|
|
726
|
|
|
control.rows = newRows; |
727
|
|
|
control.setValue( newSettings ); |
728
|
|
|
|
729
|
|
|
}, |
730
|
|
|
|
731
|
|
|
/** |
732
|
|
|
* Delete a row in the repeater setting |
733
|
|
|
* |
734
|
|
|
* @param index Position of the row in the complete Setting Array |
735
|
|
|
*/ |
736
|
|
|
deleteRow: function( index ) { |
737
|
|
|
|
738
|
|
|
'use strict'; |
739
|
|
|
|
740
|
|
|
var currentSettings = this.getValue(), |
|
|
|
|
741
|
|
|
row, |
742
|
|
|
i, |
743
|
|
|
prop; |
744
|
|
|
|
745
|
|
|
if ( currentSettings[ index ] ) { |
746
|
|
|
|
747
|
|
|
// Find the row |
748
|
|
|
row = this.rows[ index ]; |
749
|
|
|
if ( row ) { |
750
|
|
|
|
751
|
|
|
// Remove the row settings |
752
|
|
|
delete currentSettings[ index ]; |
753
|
|
|
|
754
|
|
|
// Remove the row from the rows collection |
755
|
|
|
delete this.rows[ index ]; |
756
|
|
|
|
757
|
|
|
// Update the new setting values |
758
|
|
|
this.setValue( currentSettings, true ); |
759
|
|
|
|
760
|
|
|
} |
761
|
|
|
|
762
|
|
|
} |
763
|
|
|
|
764
|
|
|
// Remap the row numbers |
765
|
|
|
i = 1; |
766
|
|
|
for ( prop in this.rows ) { |
767
|
|
|
if ( this.rows.hasOwnProperty( prop ) && this.rows[ prop ] ) { |
768
|
|
|
this.rows[ prop ].updateLabel(); |
769
|
|
|
i++; |
770
|
|
|
} |
771
|
|
|
} |
772
|
|
|
}, |
773
|
|
|
|
774
|
|
|
/** |
775
|
|
|
* Update a single field inside a row. |
776
|
|
|
* Triggered when a field has changed |
777
|
|
|
* |
778
|
|
|
* @param e Event Object |
779
|
|
|
*/ |
780
|
|
|
updateField: function( e, rowIndex, fieldId, element ) { |
781
|
|
|
|
782
|
|
|
'use strict'; |
783
|
|
|
|
784
|
|
|
var type, |
|
|
|
|
785
|
|
|
row, |
786
|
|
|
currentSettings; |
787
|
|
|
|
788
|
|
|
if ( ! this.rows[ rowIndex ] ) { |
789
|
|
|
return; |
790
|
|
|
} |
791
|
|
|
|
792
|
|
|
if ( ! this.params.fields[ fieldId ] ) { |
793
|
|
|
return; |
794
|
|
|
} |
795
|
|
|
|
796
|
|
|
type = this.params.fields[ fieldId].type; |
797
|
|
|
row = this.rows[ rowIndex ]; |
798
|
|
|
currentSettings = this.getValue(); |
799
|
|
|
|
800
|
|
|
element = jQuery( element ); |
|
|
|
|
801
|
|
|
|
802
|
|
|
if ( _.isUndefined( currentSettings[ row.rowIndex ][ fieldId ] ) ) { |
803
|
|
|
return; |
804
|
|
|
} |
805
|
|
|
|
806
|
|
|
if ( 'checkbox' === type ) { |
807
|
|
|
currentSettings[ row.rowIndex ][ fieldId ] = element.is( ':checked' ); |
808
|
|
|
} else { |
809
|
|
|
|
810
|
|
|
// Update the settings |
811
|
|
|
currentSettings[ row.rowIndex ][ fieldId ] = element.val(); |
812
|
|
|
} |
813
|
|
|
this.setValue( currentSettings, true ); |
814
|
|
|
}, |
815
|
|
|
|
816
|
|
|
/** |
817
|
|
|
* Init the color picker on color fields |
818
|
|
|
* Called after AddRow |
819
|
|
|
* |
820
|
|
|
*/ |
821
|
|
|
initColorPicker: function() { |
822
|
|
|
|
823
|
|
|
'use strict'; |
824
|
|
|
|
825
|
|
|
var control = this, |
|
|
|
|
826
|
|
|
colorPicker = control.container.find( '.color-picker-hex' ), |
827
|
|
|
options = {}, |
828
|
|
|
fieldId = colorPicker.data( 'field' ); |
829
|
|
|
|
830
|
|
|
// We check if the color palette parameter is defined. |
831
|
|
|
if ( ! _.isUndefined( fieldId ) && ! _.isUndefined( control.params.fields[ fieldId ] ) && ! _.isUndefined( control.params.fields[ fieldId ].palettes ) && _.isObject( control.params.fields[ fieldId ].palettes ) ) { |
832
|
|
|
options.palettes = control.params.fields[ fieldId ].palettes; |
833
|
|
|
} |
834
|
|
|
|
835
|
|
|
// When the color picker value is changed we update the value of the field |
836
|
|
|
options.change = function( event, ui ) { |
837
|
|
|
|
838
|
|
|
var currentPicker = jQuery( event.target ), |
|
|
|
|
839
|
|
|
row = currentPicker.closest( '.repeater-row' ), |
840
|
|
|
rowIndex = row.data( 'row' ), |
841
|
|
|
currentSettings = control.getValue(); |
842
|
|
|
|
843
|
|
|
currentSettings[ rowIndex ][ currentPicker.data( 'field' ) ] = ui.color.toString(); |
844
|
|
|
control.setValue( currentSettings, true ); |
845
|
|
|
|
846
|
|
|
}; |
847
|
|
|
|
848
|
|
|
// Init the color picker |
849
|
|
|
if ( 0 !== colorPicker.length ) { |
850
|
|
|
colorPicker.wpColorPicker( options ); |
851
|
|
|
} |
852
|
|
|
}, |
853
|
|
|
|
854
|
|
|
/** |
855
|
|
|
* Init the dropdown-pages field with selectWoo |
856
|
|
|
* Called after AddRow |
857
|
|
|
* |
858
|
|
|
* @param {object} theNewRow the row that was added to the repeater |
859
|
|
|
* @param {object} data the data for the row if we're initializing a pre-existing row |
860
|
|
|
* |
861
|
|
|
*/ |
862
|
|
|
initSelect: function( theNewRow, data ) { |
863
|
|
|
|
864
|
|
|
'use strict'; |
865
|
|
|
|
866
|
|
|
var control = this, |
|
|
|
|
867
|
|
|
dropdown = theNewRow.container.find( '.repeater-field select' ), |
868
|
|
|
$select, |
869
|
|
|
dataField, |
870
|
|
|
multiple, |
871
|
|
|
selectWooOptions = {}; |
872
|
|
|
|
873
|
|
|
if ( 0 === dropdown.length ) { |
874
|
|
|
return; |
875
|
|
|
} |
876
|
|
|
|
877
|
|
|
dataField = dropdown.data( 'field' ); |
878
|
|
|
multiple = jQuery( dropdown ).data( 'multiple' ); |
879
|
|
|
if ( 'undefed' !== multiple && jQuery.isNumeric( multiple ) ) { |
880
|
|
|
multiple = parseInt( multiple, 10 ); |
881
|
|
|
if ( 1 < multiple ) { |
882
|
|
|
selectWooOptions.maximumSelectionLength = multiple; |
883
|
|
|
} |
884
|
|
|
} |
885
|
|
|
|
886
|
|
|
data = data || {}; |
|
|
|
|
887
|
|
|
data[ dataField ] = data[ dataField ] || ''; |
888
|
|
|
|
889
|
|
|
$select = jQuery( dropdown ).selectWoo( selectWooOptions ).val( data[ dataField ] ); |
|
|
|
|
890
|
|
|
|
891
|
|
|
this.container.on( 'change', '.repeater-field select', function( event ) { |
892
|
|
|
|
893
|
|
|
var currentDropdown = jQuery( event.target ), |
|
|
|
|
894
|
|
|
row = currentDropdown.closest( '.repeater-row' ), |
895
|
|
|
rowIndex = row.data( 'row' ), |
896
|
|
|
currentSettings = control.getValue(); |
897
|
|
|
|
898
|
|
|
currentSettings[ rowIndex ][ currentDropdown.data( 'field' ) ] = jQuery( this ).val(); |
899
|
|
|
control.setValue( currentSettings ); |
900
|
|
|
}); |
901
|
|
|
} |
902
|
|
|
}); |
903
|
|
|
|
Since ECMAScript 6, you can create block-scoped vars or constants with the keywords
let
orconst
. These variables/constants are only valid in the code block where they have been declared.Consider the following two pieces of code:
and
The variable is not defined otuside of its block. This limits bleeding of variables into other contexts.
To know more about this ECMA6 feature, look at the MDN pages on let and const.